import threading
import time, os, sys, re
import string
import queue

path = '"' + os.path.dirname(sys.executable) + '\\scripts\\pip" '
try:
	from serial import Serial
except:
	os.system(path + 'install pyserial')
finally:
	import serial
	import serial.tools.list_ports as serial_listport


##########################################################################################
# serial comm commnunication class
#
class SerialCom:
	'''
	Open/close a serial port, read data to rxqueue, write data from txqueue
	'''

	def __init__(self, rxqueue=None, txqueue=None, logger=None):
		self.logger = logger
		self.scom = None
		self.port = None
		self.rxThread = None
		self.txThread = None
		self.rxThread_running = False
		self.rxQueue = rxqueue
		self.txQueue = txqueue

	# return current available com list
	def getComList(self):
		portlist = list(serial_listport.comports())
		ports = []
		if len(portlist):
			for com in portlist:
				comx = re.findall('(COM\d+)', str(com))
				ports.append(comx[0])
		return ports

	# open/close COM port
	# @return : scom, the caller may change the button text depends on the return value
	def openCom(self, port=None, baudrate=115200, rxtimeout=0.08):
		if not self.scom :
			self.port = port
			if self.port is None or not self.port.startswith("COM") :
				self.logger and self.logger("Please select a COMx.")
				print("Please select a COMx.")
				return None
			try:
				self.logger and self.logger(f"Try to open {self.port}")
				print(f"Try to open {self.port}")
				self.scom = serial.Serial(self.port, baudrate, stopbits=1, bytesize=8, timeout=rxtimeout)
			except serial.serialutil.SerialException as e:
				print(f"Serial port {self.port} failed to open.")
				self.scom = None
		return self.scom

	def closeCom(self):
		if self.scom:
			try:
				print("Try to close %s" % self.port)
				self.scom.close()
			except serial.serialutil.SerialException as e:
				print(e)
			self.scom = None
			print("Serial %s closed" % self.port)
		self.rxThread_running = False
		return self.scom

	def sendData(self, data) -> bool:
		'''
		Send data to serial sending queue.
		'''
		if not self.scom or not self.txQueue: return False
		print(data)
		self.txQueue.put(data)
		return True

	def openReadThread(self):
		if (not self.scom) or (not self.rxQueue): return
		self.rxThread = threading.Thread(target=self._readData)
		self.rxThread_running = True
		self.rxThread.daemon = True
		self.rxThread.start()

	def openWriteThread(self):
		if (not self.scom) or (not self.txQueue): return
		self.txThread = threading.Thread(target=self._writeData)
		self.txThread.daemon = True
		self.txThread.start()

	def _writeData(self):
		print("Start COM writing thread")
		while self.scom:
			data = self.txQueue.get(block=True)
			if self.scom is None: break
			try:
				if isinstance(data, (bytes, bytearray)):
					result = self.scom.write(data)
				else:
					result = self.scom.write(data.encode('UTF-8'))
			except serial.serialutil.SerialException as e:
				print(e)
				break
		print("Exit serial write thread")

	def _readData(self):
		print("Start COM receiving thread")
		while self.rxThread_running:
			try:
				length = self.scom.inWaiting()
				if length:
					data = self.scom.read(length)
					if len(data) and self.rxQueue:
						self.rxQueue.put(data)
			except serial.serialutil.SerialException as e:
				print(f'COM read err : {e}')
				self.rxThread_running = False
				break
		self.closeCom()
		print("Exit serial read thread")




